package com.unitd.base.frame.plugin.log.aop;

import com.alibaba.fastjson.JSONObject;
import com.unitd.base.frame.plugin.log.annotation.Log;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author hudan
 * @version 1.0
 * @filename LogAdvice.java
 * @copyright www.unitd.com
 * @date 2020-07-08
 */
@Aspect
@Component
public class LogAdvice {

    @Autowired
    private static final Logger logger = LoggerFactory.getLogger(LogAdvice.class);

    @Pointcut("@annotation(com.unitd.base.frame.plugin.log.annotation.Log)")
    public void serviceAspect() {
    }

    @Before("serviceAspect()")
    public void after(JoinPoint joinPoint) {
        logger.info("开始记录日志*************************");

        MethodSignature ms = (MethodSignature) joinPoint.getSignature();
        Method method = ms.getMethod();

        Log myAnnotation = method.getAnnotation(Log.class);

        Map<String, Object> paramsMap = new HashMap<>();
        // 参数名
        String[] argNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        // 参数
        Object[] argObjects = joinPoint.getArgs();

        List<String> argNameList = Arrays.asList(argNames);
        resolveArgs(joinPoint, argNameList, argObjects, paramsMap);

        saveToDbLog(myAnnotation, paramsMap);
    }

    @AfterReturning(pointcut = "serviceAspect()", returning = "returnValue")
    public void afterreturningJoinPoint(JoinPoint joinPoint, Object returnValue) {
        System.out.println("后置通知" + joinPoint + "返回值" + returnValue);
    }

    @Around("serviceAspect()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        return pjp.proceed();
    }

    public static void saveToDbLog(Log myAnnotation, Map<String, Object> paramsMap) {
        String description = myAnnotation.name();
        description = handleAnnotation(description, paramsMap);
        //TODO db操作
        System.out.println(description);
    }


    public static String handleAnnotation(String description, Map<String, Object> paramsMap) {
        AtomicReference<String> result = new AtomicReference<>(description);
        if (StringUtils.isBlank(description)) {
            return result.get();
        }
        paramsMap.entrySet().parallelStream().forEach(p -> {
            if (result.get().contains(p.getKey())) {
                result.set(result.get().replaceAll(p.getKey(), p.getValue() + ""));
            }
        });
        return result.get();
    }

    public static void resolveArgs(JoinPoint joinPoint, List<String> argNameList, Object[] argObjects,
                                   Map<String, Object> paramsMap) {
        for (int i = 0; i < argNameList.size(); i++) {
            Class<?> classo = argObjects[i].getClass();

            if (handleClassType(classo.getName())) {
                paramsMap.put(argNameList.get(i), joinPoint.getArgs()[i]);
                continue;
            }

            JSONObject object = (JSONObject) JSONObject.toJSON(joinPoint.getArgs()[i]);

            object.keySet().parallelStream().forEach(p -> {
                paramsMap.put(p, object.get(p));
            });
        }

    }

    public static <T> T get(Class<T> clz, Object o) {
        if (clz.isInstance(o)) {
            return clz.cast(o);
        }
        return null;
    }


    public static boolean handleClassType(String name) {
        AtomicBoolean result = new AtomicBoolean(false);
        String[] className = {"String", "Integer", "Long", "int", "float", "double", "char"};
        Arrays.asList(className).forEach(n -> {
            if (name.contains(n)) {
                result.set(name.contains(n));
            }
        });
        return result.get();
    }

}